'''A private module containing a parser and processors for BibTeX strings.
Used by the L{string_tools} and L{names} modules.

@copyright: (c) 2010, Benjamin Kalish

@license:
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
'''

# TO DO: Redo all docs in this file

import types

from simpleparse.parser import Parser
from simpleparse.common import chartypes
from simpleparse.dispatchprocessor import *
from bkn.bibtex.utilities import ensure_objects_are_unicode

_declaration = r'''
# BE CAREFUL WHEN EDITING THIS GRAMMAR - simpleparse has limited suport for
# backtracking and constructions such as 'a := b*, b' will not parse.

# COMMON DEFINITIONS
<eof>             := EOF
<ws>              := whitespace
escaped_chars     := '\\"' / '\\{' / '\\}'

# A brace_string is a string delimited by braces. brace_strings may be nested.
brace_string      := '{', (no_brace_string / brace_string)* , '}'
<no_brace_string> := (escaped_chars / -[{}])+

# -----------------------------------------------------------------------------

# BibTeX has an unusual way of looking at the contents of its fields in terms
# of what it considers to be a letter, and how it defines capitalization. The
# rules given below attempt to emulate this behavior.

spec_char         := brace_string / digit / [-._]
bt_letter         := (spec_char / letter)
word              := bt_letter+

<cap_word>        := (up_letter, bt_letter*) / spec_char+
<uncap_word>      := low_letter, bt_letter*

string_to_split   := (bt_letter / word_sep)*, eof
word_sep          := -bt_letter

up_letter         := uppercasechar / (?-low_letter, brace_string)
low_letter        := lowercasechar / ('{', (ws, latex_command)?, ws?, lowercasechar, (no_brace_string / brace_string)*, '}')
latex_command     := [\], -whitespacechar*

# -----------------------------------------------------------------------------

name              := (von_last_jr_first / von_last_first / first_von_last / first_last / one_word_last), eof!

von_last_jr_first := inverted_v?, inverted_l, comma, inverted_j, comma, inverted_f

von_last_first    := inverted_v?, inverted_l, comma, inverted_f
inverted_v        := (uncap_word, ws, ?word)+
inverted_f        := (word, ws?)+
inverted_l        := (word, ws?)+
inverted_j        := (word, ws?)+

first_von_last    := first_von_last_f?, first_von_last_v, first_von_last_l
first_von_last_f  := (cap_word, ws)+
first_von_last_v  := (uncap_word, ws)+
first_von_last_l  := (word, ws?)+

first_last        := first_last_f, first_last_l
first_last_f      := (cap_word, ws, ?cap_word)*
first_last_l      := cap_word

one_word_last     := word

<comma>           := ws?, ',', ws?

# ----

author_list       := ws?, (author, and)*, author, ws?, eof!
author            := ?-'and', word, (','?, ws, ?-'and', word)*

<and>             := ws, 'and', ws
'''

class NameListSplitter( DispatchProcessor ):
    '''Used to split a string on the word and according to the rules of
    BibTeX.
    
    Usage:
    
        >>> string = "{Stop and Shop} and Big Y"
        >>> list = BibtexStringParser().parse(
        ...  string,
        ...  production='author_list',
        ...  processor=NameListSplitter()
        ...  )
        >>> list
        ["{Stop and Shop}", "Big Y"]
        
    Note that substrings enclosed in braces are protected so that "{Stop
    and Shop}" will not be split.
    '''
    def __call__(self, value, buffer):
        '''Ensures that a list of strings is returned directly when
        NameList Splitter is used as a base processor.'''
        if len(value) == 3:
            (success, tags, next) = value
            return dispatchList(self, tags, buffer)
        else:
			return dispatch( self, value, buffer )
            
    def author(self, (tag,start,stop,subtags), buffer):
        '''Returns the value of the string.'''
        return getString( (tag,start,stop,subtags), buffer)

class StringSplitter( DispatchProcessor ):
    '''Used to split a string into a list of characters according to the
    rules of BibTeX.

    Note that this is not as trivial as it seems, since BibTeX considers
    a brace delimited substring to be a single character.
    
    Useage:
    
        >>> chars = BibtexStringParser().parse(
        ...  string,
        ...  production='string_to_split',
        ...  processor=StringSplitter()
        ...  )
        
    '''
    def __call__(self, value, buffer):
        '''Ensures that a list of strings is returned directly when 
        StringSplitter is used as a base processor.'''
        if len(value) == 3:
            (success, tags, next) = value
            return dispatchList(self, tags, buffer)
        else:
			return dispatch( self, value, buffer )
    
    def bt_letter(self, (tag,start,stop,subtags), buffer):
        return getString( (tag,start,stop,subtags), buffer).strip()
        
    def word_sep(self, (tag,start,stop,subtags), buffer):
        return ' '

			
class BibtexNameProcessor( DispatchProcessor ):
    '''A subclass of simpleparse.DispatchProcessor for processing
    BibtexNameParser result trees.
    
    BibtexProcessor will be used by default when you call parse() on a
    BibtexParser object.
    '''    
    def __init__(self):
        self.first = ''
        self.last = ''
        self.jr = ''
        self.von = ''

    def __call__(self, value, buffer):
        if len(value) == 3:
            (success, tags, next) = value
            return dispatchList(self, tags, buffer)[0]
        else:
			return dispatch( self, value, buffer )
                        
    def name(self, (tag,start,stop,subtags), buffer):
        for item in subtags:
            dispatch(self, item, buffer)
        return (self.first, self.von, self.last, self.jr)

    # von part, last part, comma, jr, comma, and first part
    
    def von_last_jr_first(self, (tag,start,stop,subtags), buffer):
        for item in subtags:
            dispatch(self, item, buffer)
        return (self.first, self.von, self.last, self.jr)
   
    # von part, last part, comma, and first part
    
    def von_last_first(self, (tag,start,stop,subtags), buffer):
        for item in subtags:
            dispatch(self, item, buffer)
        return (self.first, self.von, self.last, self.jr)

    def inverted_f(self, (tag,start,stop,subtags), buffer):
        self.first = getString( (tag,start,stop,subtags), buffer).strip()

    def inverted_l(self, (tag,start,stop,subtags), buffer):
        self.last = getString( (tag,start,stop,subtags), buffer).strip()

    def inverted_v(self, (tag,start,stop,subtags), buffer):
        self.von = getString( (tag,start,stop,subtags), buffer).strip()

    def inverted_j(self, (tag,start,stop,subtags), buffer):
        self.jr = getString( (tag,start,stop,subtags), buffer).strip()


    # first names, von part, and last name

    def first_von_last(self, (tag,start,stop,subtags), buffer):
        for item in subtags:
            dispatch(self, item, buffer)
        return (self.first, self.von, self.last, self.jr)

    def first_von_last_f(self, (tag,start,stop,subtags), buffer):
        self.first = getString( (tag,start,stop,subtags), buffer).strip()

    def first_von_last_v(self, (tag,start,stop,subtags), buffer):
        self.von = getString( (tag,start,stop,subtags), buffer).strip()

    def first_von_last_l(self, (tag,start,stop,subtags), buffer):
        self.last = getString( (tag,start,stop,subtags), buffer).strip()

    # first names and one word last name

    def first_last(self, (tag,start,stop,subtags), buffer):
        for item in subtags:
            dispatch(self, item, buffer)
        return (self.first, self.von, self.last, self.jr)

    def first_last_f(self, (tag,start,stop,subtags), buffer):
        self.first = getString( (tag,start,stop,subtags), buffer).strip()
        
    def first_last_l(self, (tag,start,stop,subtags), buffer):
        self.last = getString( (tag,start,stop,subtags), buffer).strip()

    # one word last name 

    def one_word_last(self, (tag,start,stop,subtags), buffer):
        self.last = getString( (tag,start,stop,subtags), buffer).strip()
        return (self.first, self.von, self.last, self.jr)

class BibtexStringParser( Parser ):
    '''A subclass of simpleparse.Parser for parseing and processing
    BibTeX data.

    BibtexParser uses BibtexProcessor as its default processor and 'top'
    as its default production, but these can be changed by passing the
    appropriate arguments to parse(). See the simpleparse documentation
    for more information.
    '''
    def __init__(self):
        # Loads the bibtex grammar.
        Parser.__init__(self, _declaration)
        
    def parse(self, data, production=None, processor=None, start=0, stop=None):
        '''Parses and processes a bibtex string and returns the result.
        Depending on the production used, this may split the string into
        according to BibTeX's criteria for characters, words, names, or
        other criteria.'''
        ensure_objects_are_unicode(data)
        try:
            data = data.encode('utf_8') 
        except UnicodeEncodeError: 
            raise BKNBibtexError('BibtexStringParser.parse() could not ' +
                 'convert string to utf_8.')
        if production==None:
            production = 'top'
        result = Parser.parse(self, data, production, processor, start, stop)
        result = [string.decode('utf_8') for string in result]
        return result

BIBTEX_STRING_PARSER = BibtexStringParser()
'''A BibtexStringParser object ready for use.'''